home *** CD-ROM | disk | FTP | other *** search
/ Reverse Code Engineering RCE CD +sandman 2000 / ReverseCodeEngineeringRceCdsandman2000.iso / RCE / Bill's Win32Asm Page / com_2.txt < prev    next >
Text File  |  2000-05-25  |  18KB  |  573 lines

  1. COM in ASM - Part 2
  2. ------------------------------------------------------------------------------
  3.  
  4. My previous atricle described how to use COM objects in your assembly
  5. language programs.  It described only how to call COM methods, but not how to
  6. create your own COM objects.  This article will describe how to do that.
  7.  
  8. This article will describe implementing COM Objects, using MASM syntax.  TASM
  9. or NASM assemblers will not be considered, however the methods can be easily
  10. applied to any assembler.  
  11.  
  12. This article will also not describe some of the more advanced features of COM
  13. such as reuse, threading, servers/clients, and so on.  These will presented
  14. in future articles.
  15.  
  16.  
  17. COM Interfaces Review
  18. ------------------------------------------------------------------------------
  19.  
  20. An interface definition specifies the interface's methods, their return types, 
  21. the number and types of their parameters, and what the methods must do.  Here 
  22. is a sample interface definition:
  23.  
  24. IInterface struct
  25.     lpVtbl  dd  ?
  26. IInterface ends
  27.  
  28. IInterfaceVtbl struct
  29.     ; IUnknown methods
  30.     STDMETHOD       QueryInterface, :DWORD, :DWORD, :DWORD
  31.     STDMETHOD       AddRef, :DWORD
  32.     STDMETHOD       Release, :DWORD
  33.     ; IInterface methods
  34.     STDMETHOD       Method1, :DWORD
  35.     STDMETHOD       Method2, :DWORD
  36. IInterfaceVtbl ends
  37.  
  38. STDMETHOD is used to simplify the interface declaration, and is defined as:
  39.  
  40. STDMETHOD MACRO name, argl :VARARG
  41.     LOCAL @tmp_a
  42.     LOCAL @tmp_b
  43.     @tmp_a TYPEDEF PROTO argl
  44.     @tmp_b TYPEDEF PTR @tmp_a
  45.     name @tmp_b ?
  46. ENDM
  47.  
  48. This macro is used to greatly simplify interface declarations, and so that the
  49. MASM invoke syntax can be used. (Macro originally by Ewald :)
  50.  
  51. Access to the interface's methods occurs through a pointer.  This pointer
  52. points to a table of function pointers, called a vtable. Here is a sample 
  53. method call:
  54.  
  55. mov     eax, [lpif]                            ; lpif is the interface pointer
  56. mov     eax, [eax]                             ; get the address of the vtable
  57. invoke  (IInterfaceVtbl [eax]).Method1, [lpif] ; indirect call to the function
  58. - or -
  59. invoke  [eax][IInterfaceVtbl.Method2], [lpif]  ; alternate notation
  60.  
  61. Two different styles of addressing the members are shown.  Both notations
  62. produce equivalent code, so the method used is a matter of personal
  63. preference.
  64.  
  65. All interfaces must inherit from the IUnknown interface.  This means that the
  66. first 3 methods of the vtable must be QueryInterface, AddRef, and Release.
  67. The purpose and implementation of these methods will be discussed later.
  68.  
  69.  
  70. GUIDS
  71. ------------------------------------------------------------------------------
  72.  
  73. A GUID is a Globally Unique ID.  A GUID is a 16-byte number, that is unique 
  74. to an interface.  COM uses GUID's to identify different interfaces from one
  75. another.  Using this method prevents name clashing as well as version
  76. clashing.  To get a GUID, you use a generator utility that is included with
  77. most win32 development packages.  
  78.  
  79. A GUID is represented by the following structure:
  80.  
  81. GUID STRUCT
  82.     Data1   dd ?
  83.     Data2   dw ?
  84.     Data3   dw ?
  85.     Data4   db 8 dup(?)
  86. GUID ENDS
  87.  
  88. A GUID is then defined in the data section:
  89. MyGUID GUID <3F2504E0h, 4f89h, 11D3h, <9Ah, 0C3h, 0h, 0h, 0E8h, 2Ch, 3h, 1h>>
  90.  
  91. Once a GUID is assigned to an interface and published, no furthur changes to 
  92. the interface definition are allowed.  Note, that this does mean that the 
  93. interface implementation may not change, only the definition.  For changes
  94. to the interface definition, a new GUID must be assigned.
  95.  
  96.  
  97. COM Objects
  98. ------------------------------------------------------------------------------
  99.  
  100. A COM object is simply an implementation of an interface.  Implentation 
  101. details are not covered by the COM standard, so we are free to implement our
  102. objects as we choose, so long as they satisfy all the requirements of the 
  103. interface definition.  
  104.  
  105. A typical object will contain pointers to the various interfaces it supports, 
  106. a reference count, and any other data that the object needs.  Here is a sample
  107. object definition, implemented as a structure:
  108.  
  109. Object struct
  110.     interface   IInterface  <?>     ; pointer to an IInterface
  111.     nRefCount   dd          ?       ; reference count
  112.     nValue      dd          ?       ; private object data
  113. Object ends
  114.  
  115. We also have to define the vtable's we are going to be using.  These tables
  116. must be static, and cannot change during run-time.  Each member of the vtable
  117. is a pointer to a method.  Following is a method for defining the vtable. 
  118.  
  119. @@IInterface segment dword
  120. vtblIInterface:
  121.     dd      offset IInterface@QueryInterface
  122.     dd      offset IInterface@AddRef
  123.     dd      offset IInterface@Release
  124.     dd      offset IInterface@GetValue
  125.     dd      offset IInterface@SetValue
  126. @@IInterface ends
  127.  
  128.  
  129. Reference Counting
  130. ------------------------------------------------------------------------------
  131.  
  132. COM object manage their lifetimes through reference counting.  Each object
  133. maintains a reference count that keeps track of how many instances of the
  134. interface pointer have been created.  The object is required to keep a 
  135. counter that supports 2^32 instances, meaning the reference count must be a
  136. DWORD.  
  137.  
  138. When the reference count drops to zero, the object is no longer in use, and
  139. it destroys itself.  The 2 IUnknown methods AddRef and Release handle the
  140. reference counting for a COM object.
  141.  
  142.  
  143. QueryInterface
  144. ------------------------------------------------------------------------------
  145.  
  146. The QueryInterface method is used by a COM object to determine if the object
  147. supports a given interface, and then if supported, to get the interface 
  148. pointer.  There are 3 rules to implementing the QueryInterface method:
  149.    
  150.     1. Objects must have an identity - a call to QueryInterface must always
  151.        return the same pointer value.
  152.     2. The set of interfaces of an object must never change - for example, if
  153.        a call to QueryInterface with on IID succeeds once, it must succeed 
  154.        always.  Likewise, if it fails once, it must fail always.
  155.     3. It must be possible to successfully query an interface of an object
  156.        from any other interface.
  157.  
  158. QueryInterface returns a pointer to a specified interface on an object to 
  159. which a client currently holds an interface pointer. This function must call 
  160. the AddRef method on the pointer it returns. 
  161.  
  162. Following are the QueryInterface parameters:
  163.     pif  : [in] a pointer to the calling interface
  164.     riid : [in] pointer to the IID of the interface being queried
  165.     ppv  : [out] pointer to the pointer of the interface that is to be set.  
  166.            If the interface is not supported, the pointed to value is set to 0
  167.  
  168. QueryInterface returns the following:
  169.    S_OK if the interface is supported
  170.    E_NOINTERFACE if not supported
  171.  
  172. Here is a simple assembly implementation of QueryInterface:
  173.  
  174. IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
  175.     ; The following compares the requested IID with the available ones.  
  176.     ; In this case, because IInterface inherits from IUnknown, the IInterface
  177.     ; interface is prefixed with the IUnknown methods, and these 2 interfaces
  178.     ; share the same interface pointer.
  179.     invoke  IsEqualGUID, [riid], addr IID_IInterface
  180.     or      eax,eax
  181.     jnz     @1
  182.     invoke  IsEqualGUID, [riid], addr IID_IUnknown
  183.     or      eax,eax
  184.     jnz     @1
  185.     jmp     @NoInterface
  186.     
  187. @1:     
  188.     ; GETOBJECTPOINTER is a macro that will put the object pointer into eax, 
  189.     ; when given the name of the object, the name of the interface, and the
  190.     ; interface pointer.
  191.     GETOBJECTPOINTER    Object, interface, pif    
  192.  
  193.     ; now get the pointer to the requested interface
  194.     lea     eax, (Object ptr [eax]).interface
  195.     
  196.     ; set *ppv with this interface pointer
  197.     mov     ebx, [ppv]
  198.     mov     dword ptr [ebx], eax
  199.     
  200.     ; increment the reference count by calling AddRef
  201.     GETOBJECTPOINTER    Object, interface, pif
  202.     mov     eax, (Object ptr [eax]).interface
  203.     invoke  (IInterfaceVtbl ptr [eax]).AddRef, pif
  204.     
  205.     ; return S_OK
  206.     mov     eax, S_OK
  207.     jmp     return
  208.  
  209. @NoInterface:
  210.     ; interface not supported, so set *ppv to zero
  211.     mov     eax, [ppv]
  212.     mov     dword ptr [eax], 0
  213.     
  214.     ; return E_NOINTERFACE
  215.     mov     eax, E_NOINTERFACE
  216.         
  217. return: 
  218.     ret
  219. IInterface@QueryInterface endp
  220.  
  221.  
  222. AddRef
  223. ------------------------------------------------------------------------------
  224.  
  225. The AddRef method is used to increment the reference count for an interface
  226. of an object.  It should be called for every new copy of an interface pointer
  227. to an object.  
  228.  
  229. AddRef takes no parameters, other than the interface pointer required for all
  230. methods.  AddRef should return the new reference count.  However, this value
  231. is to be used by callers only for testing purposes, as it may be unstable in
  232. certain situations.
  233.  
  234. Following is a simple implementation of the AddRef method:
  235.  
  236. IInterface@AddRef proc pif:DWORD
  237.     GETOBJECTPOINTER    Object, interface, pif
  238.     ; increment the reference count
  239.     inc     [(Object ptr [eax]).nRefCount]
  240.     ; now return the count
  241.     mov     eax, [(Object ptr [eax]).nRefCount]
  242.     ret
  243. IInterface@AddRef endp
  244.  
  245.  
  246. Release
  247. ------------------------------------------------------------------------------
  248.  
  249. Release decrements the reference count for the calling interface on a object. 
  250. If the reference count on the object is decrememnted to 0, then the object is 
  251. freed from memory.  This function should be called when you no longer need to 
  252. use an interface pointer
  253.  
  254. Like AddRef, Release takes only one parameter - the interface pointer.  It
  255. also returns the current value of the reference count, which, similarly, is to
  256. be used for testing purposess only
  257.  
  258. Here is a simple implementation of Release:
  259.  
  260. IInterface@Release proc pif:DWORD
  261.     GETOBJECTPOINTER    Object, interface, pif
  262.  
  263.     ; decrement the reference count
  264.     dec     [(Object ptr [eax]).nRefCount]  
  265.     
  266.     ; check to see if the reference count is zero.  If it is, then destroy
  267.     ; the object.
  268.     mov     eax, [(Object ptr [eax]).nRefCount]
  269.     or      eax, eax
  270.     jnz     @1
  271.  
  272.     ; free the object: here we have assumed the object was allocated with
  273.     ; LocalAlloc and with LMEM_FIXED option
  274.     GETOBJECTPOINTER    Object, interface, pif
  275.     invoke  LocalFree, eax
  276. @1:
  277.     ret
  278. IInterface@Release endp
  279.  
  280.  
  281. Creating a COM object
  282. ------------------------------------------------------------------------------
  283.  
  284. Creating an object consisits basically of allocating the memory for the 
  285. object, and then initializeing its data members.  Typically, the vtable 
  286. pointer is initialized and the reference count is zeroed.  QueryInterface 
  287. could then be called to get the interface pointer.
  288.  
  289. Other methods exist for creating objects, such as using CoCreateInstance, and
  290. using class factories.  These methods will not be discussed, and may be a 
  291. topic for a future article.
  292.  
  293.  
  294. COM implementatiion sample application
  295. ------------------------------------------------------------------------------
  296.  
  297. Here follows a sample implementation and usage of a COM object.  It shows how
  298. to create the object, call its methods, then free it.  It would probably be 
  299. very educational to assemble this and run it through a debugger.  This and 
  300. other examples can be found at http://asm.tsx.org.
  301.  
  302.  
  303. .386
  304. .model flat,stdcall
  305.  
  306. include windows.inc
  307. include kernel32.inc 
  308. include user32.inc  
  309.  
  310. includelib kernel32.lib       
  311. includelib user32.lib   
  312. includelib uuid.lib
  313.  
  314. ;-----------------------------------------------------------------------------
  315.  
  316. ; Macro to simply interface declarations
  317. ; Borrowed from Ewald, http://here.is/diamond/
  318. STDMETHOD   MACRO   name, argl :VARARG
  319. LOCAL @tmp_a
  320. LOCAL @tmp_b
  321. @tmp_a  TYPEDEF PROTO argl
  322. @tmp_b  TYPEDEF PTR @tmp_a
  323. name    @tmp_b      ?
  324. ENDM
  325.  
  326. ; Macro that takes an interface pointer and returns the implementation 
  327. ; pointer in eax
  328. GETOBJECTPOINTER MACRO Object, Interface, pif
  329.     mov     eax, pif
  330.     IF (Object.Interface)
  331.         sub     eax, Object.Interface
  332.     ENDIF
  333. ENDM
  334.  
  335. ;-----------------------------------------------------------------------------
  336.  
  337. IInterface@QueryInterface   proto :DWORD, :DWORD, :DWORD
  338. IInterface@AddRef           proto :DWORD
  339. IInterface@Release          proto :DWORD
  340. IInterface@Get              proto :DWORD
  341. IInterface@Set              proto :DWORD, :DWORD
  342.  
  343. CreateObject                proto :DWORD
  344. IsEqualGUID                 proto :DWORD, :DWORD
  345.  
  346. externdef                   IID_IUnknown:GUID
  347.  
  348. ;-----------------------------------------------------------------------------
  349.  
  350. ; declare the interface prototype
  351. IInterface struct
  352.     lpVtbl  dd  ?
  353. IInterface ends
  354.  
  355. IInterfaceVtbl struct
  356.     ; IUnknown methods
  357.     STDMETHOD       QueryInterface, pif:DWORD, riid:DWORD, ppv:DWORD
  358.     STDMETHOD       AddRef, pif:DWORD
  359.     STDMETHOD       Release, pif:DWORD
  360.     ; IInterface methods
  361.     STDMETHOD       GetValue, pif:DWORD
  362.     STDMETHOD       SetValue, pif:DWORD, val:DWORD
  363. IInterfaceVtbl ends
  364.  
  365.  
  366. ; declare the object structure
  367. Object struct
  368.     ; interface object
  369.     interface   IInterface  <?>
  370.  
  371.     ; object data
  372.     nRefCount   dd          ?
  373.     nValue      dd          ?
  374. Object ends
  375.  
  376. ;-----------------------------------------------------------------------------
  377.  
  378. .data
  379. ; define the vtable
  380. @@IInterface segment dword
  381. vtblIInterface:
  382.     dd      offset IInterface@QueryInterface
  383.     dd      offset IInterface@AddRef
  384.     dd      offset IInterface@Release
  385.     dd      offset IInterface@GetValue
  386.     dd      offset IInterface@SetValue
  387. @@IInterface ends
  388.  
  389. ; define the interface's IID
  390. ; {CF2504E0-4F89-11d3-9AC3-0000E82C0301}
  391. IID_IInterface GUID <0cf2504e0h, 04f89h, 011d3h, <09ah, 0c3h, 00h, 00h, 0e8h, 02ch, 03h, 01h>>
  392.  
  393. ;-----------------------------------------------------------------------------
  394.  
  395. .code
  396. start:
  397. StartProc proc  
  398.     LOCAL   pif:DWORD       ; interface pointer
  399.  
  400.     ; create the object
  401.     invoke  CreateObject, addr [pif]
  402.     or      eax,eax
  403.     js      exit
  404.     
  405.     ; call the SetValue method
  406.     mov     eax, [pif]
  407.     mov     eax, [eax]
  408.     invoke  (IInterfaceVtbl ptr [eax]).SetValue, [pif], 12345h
  409.     
  410.     ; call the GetValue method
  411.     mov     eax, [pif]
  412.     mov     eax, [eax]
  413.     invoke  (IInterfaceVtbl ptr [eax]).GetValue, [pif]
  414.     
  415.     ; release the object
  416.     mov     eax, [pif]
  417.     mov     eax, [eax]
  418.     invoke  (IInterfaceVtbl ptr [eax]).Release, [pif]
  419.  
  420. exit:
  421.     ret
  422. StartProc endp
  423.  
  424. ;-----------------------------------------------------------------------------
  425.     
  426. IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
  427.     invoke  IsEqualGUID, [riid], addr IID_IInterface
  428.     test    eax,eax
  429.     jnz     @F
  430.     invoke  IsEqualGUID, [riid], addr IID_IUnknown
  431.     test    eax,eax
  432.     jnz     @F
  433.     jmp     @Error
  434.     
  435. @@:     
  436.     GETOBJECTPOINTER    Object, interface, pif
  437.     lea     eax, (Object ptr [eax]).interface
  438.     
  439.     ; set *ppv
  440.     mov     ebx, [ppv]
  441.     mov     dword ptr [ebx], eax
  442.     
  443.     ; increment the reference count
  444.     GETOBJECTPOINTER    Object, interface, pif
  445.     mov     eax, (Object ptr [eax]).interface
  446.     invoke  (IInterfaceVtbl ptr [eax]).AddRef, [pif]
  447.     
  448.     ; return S_OK
  449.     mov     eax, S_OK
  450.     jmp     return
  451.  
  452. @Error:
  453.     ; error, interface not supported
  454.     mov     eax, [ppv]
  455.     mov     dword ptr [eax], 0
  456.     mov     eax, E_NOINTERFACE
  457.         
  458. return: 
  459.     ret
  460. IInterface@QueryInterface endp
  461.  
  462.  
  463. IInterface@AddRef proc pif:DWORD
  464.     GETOBJECTPOINTER    Object, interface, pif
  465.     inc     [(Object ptr [eax]).nRefCount]
  466.     mov     eax, [(Object ptr [eax]).nRefCount]
  467.     ret
  468. IInterface@AddRef endp
  469.  
  470.  
  471. IInterface@Release proc pif:DWORD
  472.     GETOBJECTPOINTER    Object, interface, pif
  473.     dec     [(Object ptr [eax]).nRefCount]  
  474.     mov     eax, [(Object ptr [eax]).nRefCount]
  475.     or      eax, eax
  476.     jnz     @1
  477.     ; free object
  478.     mov     eax, [pif]
  479.     mov     eax, [eax]
  480.     invoke  LocalFree, eax
  481. @1:
  482.     ret
  483. IInterface@Release endp
  484.  
  485.  
  486. IInterface@GetValue proc pif:DWORD
  487.     GETOBJECTPOINTER    Object, interface, pif
  488.     mov     eax, (Object ptr [eax]).nValue
  489.     ret
  490. IInterface@GetValue endp
  491.  
  492.  
  493. IInterface@SetValue proc uses ebx pif:DWORD, val:DWORD
  494.     GETOBJECTPOINTER    Object, interface, pif
  495.     mov     ebx, eax
  496.     mov     eax, [val]
  497.     mov     (Object ptr [ebx]).nValue, eax  
  498.     ret
  499. IInterface@SetValue endp
  500.  
  501. ;-----------------------------------------------------------------------------
  502.  
  503. CreateObject proc uses ebx ecx pobj:DWORD
  504.     ; set *ppv to 0
  505.     mov     eax, pobj
  506.     mov     dword ptr [eax], 0
  507.  
  508.     ; allocate object
  509.     invoke  LocalAlloc, LMEM_FIXED, sizeof Object
  510.     or      eax, eax
  511.     jnz     @1
  512.     ; alloc failed, so return
  513.     mov     eax, E_OUTOFMEMORY
  514.     jmp     return
  515. @1: 
  516.  
  517.     mov     ebx, eax
  518.     mov     (Object ptr [ebx]).interface.lpVtbl, offset vtblIInterface
  519.     mov     (Object ptr [ebx]).nRefCount, 0
  520.     mov     (Object ptr [ebx]).nValue, 0
  521.  
  522.     ; Query the interface
  523.     lea     ecx, (Object ptr [ebx]).interface
  524.     mov     eax, (Object ptr [ebx]).interface.lpVtbl
  525.     invoke  (IInterfaceVtbl ptr [eax]).QueryInterface, 
  526.             ecx, 
  527.             addr IID_IInterface, 
  528.             [pobj]
  529.     cmp     eax, S_OK
  530.     je      return
  531.  
  532.     ; error in QueryInterface, so free memory
  533.     push    eax     
  534.     invoke  LocalFree, ebx
  535.     pop     eax
  536.     
  537. return:
  538.     ret
  539. CreateObject endp
  540.  
  541. ;-----------------------------------------------------------------------------
  542.  
  543. IsEqualGUID proc rguid1:DWORD, rguid2:DWORD
  544.     cld
  545.     mov     esi, [rguid1]
  546.     mov     edi, [rguid2]
  547.     mov     ecx, sizeof GUID / 4
  548.     repe    cmpsd
  549.     xor     eax, eax
  550.     or      ecx, ecx
  551.     setz    al
  552.     ret
  553. IsEqualGUID endp
  554.  
  555. end start
  556.  
  557.  
  558. Conclusion
  559. ------------------------------------------------------------------------------
  560.  
  561. We have (hopefully) seen how to implement a COM object.  We can see that it
  562. is a bit messy to do, and adds quite some overhead to our programs.  However,
  563. it can also add great flexibility and power to our programs.
  564.  
  565. Remember that COM defines only interfaces, and implementation is left to the
  566. programmer.  This article presents only one possible implementation.  This is
  567. not the only method, nor is it the best one.  The reader should feel free to 
  568. experiment with other methods.
  569.  
  570. -----------------------------------------------------------------------------
  571.  
  572. Copyright (C) 1999
  573. Bill T.  (billasm@usa.net)